use std::{rc::Rc, cell::RefCell};
use std::collections::BTreeMap;
use std::net::Ipv4Addr;
use std::str::FromStr;
use crate::port_mapper::*;
use crate::config_file::*;
use cursive::{Cursive, CursiveExt};
use cursive::traits::{Boxable, Nameable};
use cursive::view::{Finder, SizeConstraint};
use cursive::views::*;
use cursive::View;
use cursive::direction::Direction;
use log::{log, Level};

const N_LISTVIEW_MAPS: &str = "listview_maps";
const N_DIALOG_MAIN: &str = "dialog_main";
const N_EDITVIEW_NAME: &str = "editview_name";
const N_EDITVIEW_LSTADDR: &str = "editview_lstaddr";
const N_EDITVIEW_LSTPORT: &str = "editview_lstport";
const N_EDITVIEW_DSTADDR: &str = "editview_dstaddr";
const N_EDITVIEW_DSTPORT: &str = "editview_dstport";
const N_TEXTVIEW_STATUS: &str = "textview_status";
const N_BUTTON_MAP_ACTION: &str = "button_map_action";
const N_BUTTON_MAP_REMOVE: &str = "button_map_remove";
const N_BUTTON_MAP_EDIT: &str = "button_map_edit";
const N_TEXTVIEW_INFO: &str = "textview_info";

const N_STATUS_RUNNING: &str = "running";
const N_STATUS_STOPPED: &str = "stopped";
const N_ACTION_START: &str = "start";
const N_ACTION_STOP: &str = "stop";

const N_CONFIG_FILE: &str = "xportmapper.json";

#[derive(Clone)]
struct MapData {
    is_running: bool,
    id: u32,
    name: String,
    info: MapInfo,
}

impl MapData {
    fn new(id: u32, name: &str, info: MapInfo) -> Self {
        MapData {
            is_running: false,
            id,
            name: name.into(),
            info,
        }
    }
}

struct TuiData {
    maps: BTreeMap<u32, MapData>,
    max_id: u32,
    rt: MapperRuntime,
}

type TuiDataRef = Rc<RefCell<TuiData>>;

impl TuiData {
    fn new() -> Self {
        TuiData {
            maps: Default::default(),
            max_id: 0,
            rt: MapperRuntimeBuilder::new().build(),
        }
    }

    fn maps_from_config(config: &ConfigRoot) -> (u32, BTreeMap<u32, MapData>) {
        let mut maps: BTreeMap<u32, MapData> = Default::default();
        let mut max_id = 0;

        for item in &config.maps[..] {
            if !validate_ipv4(&item.lst_addr) || !validate_ipv4(&item.dst_addr) {
                continue;
            }
            if maps.contains_key(&item.id) {
                continue;
            }

            let map_data = MapData::new(item.id, &item.name,
                                        MapInfo::new(&item.lst_addr, item.lst_port,
                                                     &item.dst_addr, item.dst_port));
            maps.insert(item.id, map_data);
            if item.id > max_id {
                max_id = item.id;
            }
        }

        (max_id, maps)
    }

    fn maps_to_config(maps: &BTreeMap<u32, MapData>) -> ConfigRoot {
        let mut config = ConfigRoot::new();
        let mut id = 1;

        for (_, MapData {info, name, ..}) in maps {
            let item = ConfigItem {
                name: name.clone(),
                lst_addr: info.lst_addr.clone(),
                lst_port: info.lst_port,
                dst_addr: info.dst_addr.clone(),
                dst_port: info.dst_port,
                id
            };
            config.maps.push(item);
            id += 1;
        }

        config
    }

    fn save_to_config_file(&self) -> Result<(), Error> {
        let config = TuiData::maps_to_config(&self.maps);
        let r = save_to_file(N_CONFIG_FILE, &config);
        if let Err(ref e) = r {
            println!("Failed to save to config file: {:?}", e);
        }
        r
    }

    fn add_map(&mut self, siv: &mut Cursive, name: &str, info: MapInfo) -> Result<(), Error> {
        // Update model
        self.max_id += 1;
        let map_data = MapData::new(self.max_id, name.into(), info.clone());
        self.maps.insert(self.max_id, map_data.clone());

        // Update view
        siv.add_map(self.max_id, &map_data);
        Ok(())
    }

    // You must stop the map before you can remove it, otherwise this
    // return Error
    fn remove_map(&mut self, siv: &mut Cursive, id: u32) -> Result<MapData, Error> {
        if let Some(v) = self.maps.get(&id) {
            if v.is_running {
                Err(Error::TuiError("This map is still running".into()))
            } else {
                // Update model
                let map_data = v.clone();
                let r = Ok(self.maps.remove(&id).unwrap());
                // Update view
                siv.remove_map(&map_data);
                r
            }
        } else {
            Err(Error::TuiError("Map not exist".into()))
        }
    }

    fn edit_map(&mut self, siv: &mut Cursive, id: u32, name: &str, info: &MapInfo) -> Result<(), Error> {
        if let Some(v) = self.maps.get_mut(&id) {
            if v.is_running {
                return Err(Error::TuiError("Faile to edit a running map".into()));
            }
            // Update model
            v.name = name.into();
            v.info = info.clone();
            // Update view
            siv.edit_map(v);
            return Ok(());
        } else {
            return Err(Error::TuiError("Map not exit".into()));
        }
    }

    // on_exist would run on other thread
    fn run_map(&mut self, siv: &mut Cursive, id: u32) -> Result<(), Error> {
        if !self.maps.contains_key(&id) {
            return Err(Error::TuiError("Map not exist!".into()));
        }
        let map_data = self.maps.get(&id).unwrap();
        let _info = map_data.info.clone();
        let sender = siv.cb_sink().clone();
        let r = self.rt.map(_info.clone(), move || {
            let _ = sender.send(Box::new(move |siv| {
                // Map exit, should update model and view
                let data_ref = siv.user_data::<TuiDataRef>().unwrap().clone();
                let mut data = data_ref.borrow_mut();
                let map_data = data.maps.get_mut(&id).unwrap();

                if map_data.is_running == false {
                    // Exit normally
                    return;
                }

                // Update model
                map_data.is_running = false;
                // Update view
                siv.set_map_status(map_data, map_data.is_running);
                // we should still unmap explicitlly, otherwise we would failed
                // to map next time.
                let _ = data.rt.unmap(_info.clone());

                show_msg(siv, MsgType::Error, "Map exit unconditionally", |_| {});
            }));
        });
        let mut map_data = self.maps.get_mut(&id).unwrap();
        std::assert!(map_data.is_running == false);
        if let Ok(_) = r {
            // Update model
            map_data.is_running = true;
            // Update view
            siv.set_map_status(map_data, map_data.is_running);
        }
        r
    }

    fn stop_map(&mut self, siv: &mut Cursive, id: u32) -> Result<(), Error> {
        if let Some(v) = self.maps.get_mut(&id) {
            if !v.is_running {
                return Err(Error::TuiError("This map is not running".into()));
            }
            v.is_running = false;
            let _ = self.rt.unmap(v.info.clone());
            siv.set_map_status(v, v.is_running);
            Ok(())
        } else {
            Err(Error::TuiError("Map not exist".into()))
        }
    }
}

pub fn tui_run() -> Result<(), Box<dyn std::error::Error>> {
    let data: TuiDataRef = Rc::new(RefCell::new(TuiData::new()));
    let mut data_ref = data.borrow_mut();
    init_data(&mut data_ref);

    let mut siv = Cursive::new();
    init_ui(&mut siv);

    setup_ui_with_data(&mut siv, &mut data_ref);
    drop(data_ref);
    siv.set_user_data(data);
    siv.run();
    Ok(())
}

fn init_data(_data: &mut TuiData) {
    // TODO: Parse arguements
    let config = if let Ok(c) = parse_from_file(N_CONFIG_FILE) {
        c
    } else {
        return;
    };
    let (max_id, maps) = TuiData::maps_from_config(&config);
    _data.max_id = max_id;
    _data.maps = maps;
}

fn init_ui(siv: &mut Cursive) {
    cursive::logger::init();
    log!{Level::Error, "The test log"};
    let content = ListView::new().with_name(N_LISTVIEW_MAPS);
    let dialog = Dialog::new()
                    .title("XPortMapper")
                    .content(content)
                    .button("Add", |siv| {
                        map_editor(siv, None, |siv, v| {
                            // Check if the user enter some data
                            if let Some((name, info)) = v {
                                let data_ref = siv.user_data::<TuiDataRef>().unwrap().clone();
                                let mut data = data_ref.borrow_mut();
                                if let Err(e) = data.add_map(siv, &name, info) {
                                    show_msg(siv, MsgType::Error, &format!("Failed to add map: {:?}", e), |_| {});
                                }
                            }
                        });
                    })
                    .button("Save", |siv| {
                        let data_ref = siv.user_data::<TuiDataRef>().unwrap().clone();
                        let data = data_ref.borrow_mut();
                        let r = data.save_to_config_file();
                        drop(data);
                        if let Err(ref e) = r {
                            show_msg(siv, MsgType::Error, &format!("Failed to save: {:?}", e), |_| {});
                        } else {
                            show_msg(siv, MsgType::Note, "Save config succeed.", |_| {});
                        }
                    })
                    .button("Save and quit", |siv| {
                        // Save current state to config file
                        let data_ref = siv.user_data::<TuiDataRef>().unwrap().clone();
                        let data = data_ref.borrow_mut();
                        let _ = data.save_to_config_file();
                        siv.quit();
                    })
                    .button("Quit without saving", |siv| {
                        siv.quit();
                    })
                    .with_name(N_DIALOG_MAIN)
                    .min_size((40, 10));
    siv.add_layer(dialog);
    siv.add_global_callback('~', |siv| {
        siv.toggle_debug_console();
    });
}

fn map_editor<F>(siv: &mut Cursive, src: Option<(String, MapInfo)>, cb: F)
    where F: Fn(&mut Cursive, Option<(String, MapInfo)>) + 'static + Clone {
    let src_ref = src.as_ref();
    let cb_1 = cb.clone();
    let cb_2 = cb.clone();
    let add_dialog = Dialog::new()
        .title("Map Editor")
        .content(ListView::new()
                    .child("Name", EditView::new()
                                        .content(src_ref.map(|v| v.0.clone()).unwrap_or("".into()))
                                        .with_name(N_EDITVIEW_NAME)
                                        .fixed_width(20))
                    .child("Listen addr", EditView::new()
                                        .content(src_ref.map(|v| v.1.lst_addr.clone()).unwrap_or("".into()))
                                        .with_name(N_EDITVIEW_LSTADDR)
                                        .fixed_width(20))
                    .child("Listen port", EditView::new()
                                        .content(src_ref.map(|v| v.1.lst_port.to_string()).unwrap_or("".into()))
                                        .with_name(N_EDITVIEW_LSTPORT)
                                        .fixed_width(20))
                    .child("Dest addr", EditView::new()
                                        .content(src_ref.map(|v| v.1.dst_addr.clone()).unwrap_or("".into()))
                                        .with_name(N_EDITVIEW_DSTADDR)
                                        .fixed_width(20))
                    .child("Dest port", EditView::new()
                                        .content(src_ref.map(|v| v.1.dst_port.to_string()).unwrap_or("".into()))
                                        .with_name(N_EDITVIEW_DSTPORT)
                                        .fixed_width(20))
        )
        .button("Cancel", move |siv| {
            siv.pop_layer();
            cb_1(siv, None);
        })
        .button("Ok", move |siv| {
            match handle_map_editor_result(siv) {
                Ok(v) => {
                    // Pop the add dialog first
                    siv.pop_layer();
                    cb_2(siv, Some(v));
                },
                Err(e) => {
                    show_msg(siv, MsgType::Error, &format!("Wrong format: {:?}", e), |_| {});
                },
            }
        })
        .with_name("add_dialog");
    siv.add_layer(add_dialog);
}

fn handle_map_editor_result(siv: &mut Cursive) -> Result<(String, MapInfo), Error> {
    let editview_name = siv.find_name::<EditView>(N_EDITVIEW_NAME).unwrap();
    let editview_lstaddr = siv.find_name::<EditView>(N_EDITVIEW_LSTADDR).unwrap();
    let editview_lstport = siv.find_name::<EditView>(N_EDITVIEW_LSTPORT).unwrap();
    let editview_dstaddr = siv.find_name::<EditView>(N_EDITVIEW_DSTADDR).unwrap();
    let editview_dstport = siv.find_name::<EditView>(N_EDITVIEW_DSTPORT).unwrap();

    let name = editview_name.get_content().as_ref().clone();
    if name.len() == 0 {
        return Err(Error::TuiError("Please input name".into()));
    }

    let lstaddr: String =  editview_lstaddr.get_content().as_ref().clone();
    if !validate_ipv4(&lstaddr) {
        return Err(Error::TuiError("Wrong listen addr format!".into()));
    }
    let lstport = editview_lstport.get_content().as_ref().clone();
    let lstport = if let Ok(v) = u16::from_str(&lstport) {
        v
    } else {
        return Err(Error::TuiError("Wrong listen port format!".into()));
    };

    let dstaddr: String =  editview_dstaddr.get_content().as_ref().clone();
    if !validate_ipv4(&dstaddr) {
        return Err(Error::TuiError("Wrong dest addr format!".into()));
    }
    let dstport = editview_dstport.get_content().as_ref().clone();
    let dstport = if let Ok(v) = u16::from_str(&dstport) {
        v
    } else {
        return Err(Error::TuiError("Wrong listen port format!".into()));
    };

    Ok((name, MapInfo::new(&lstaddr, lstport, &dstaddr, dstport)))
}

fn setup_ui_with_data(siv: &mut Cursive, data: &mut TuiData) {
    for (id, map_data) in &data.maps {
        siv.add_map(*id, map_data);
    }
}

#[allow(dead_code)]
enum MsgType {
    Note,
    Warn,
    Error
}

fn show_msg<F>(siv: &mut Cursive, _type: MsgType, content: &str, on_close: F)
    where F: Fn(&mut Cursive) + 'static {
    let title = match _type {
        MsgType::Note => "NOTE",
        MsgType::Warn => "WARN",
        MsgType::Error => "ERROR"
    };

    let dialog = Dialog::new()
                    .title(title)
                    .content(TextView::new(content))
                    .button("Ok", move |siv| {
                        siv.pop_layer();
                        on_close(siv);
                    });
    siv.add_layer(dialog);
}

trait CursivePlus {
    fn siv(&mut self) -> &mut Cursive;

    fn add_map(&mut self, id: u32, map_data: &MapData) {
        let siv = self.siv();
        let info = &map_data.info;
        let item = LinearLayout::horizontal()
                    .child(TextView::new(&info.to_string()).with_name(N_TEXTVIEW_INFO)
                                                           .resized(SizeConstraint::AtLeast(45), SizeConstraint::Free))
                    .child(TextView::new(N_STATUS_STOPPED).with_name(N_TEXTVIEW_STATUS))
                    .child(DummyView {})
                    .child(Button::new(N_ACTION_START, move |siv| {
                        let id_str = id.to_string();
                        let mut item = siv.find_name::<LinearLayout>(&id_str).unwrap();
                        let btn_map_action = item.find_name::<Button>(N_BUTTON_MAP_ACTION).unwrap();
                        let label: String = btn_map_action.label().into();
                        // run_map and stop_map may find above view again, which
                        // would cause RefCell to panic, so we drop them here
                        drop(item);
                        drop(btn_map_action);

                        let data_ref = siv.user_data::<TuiDataRef>().unwrap().clone();
                        let mut data = data_ref.borrow_mut();
                        let (r, msg) = if &label == &format!("<{}>", N_ACTION_START) {
                            // Start
                            (data.run_map(siv, id), "start map")
                        } else {
                            // Stop
                            (data.stop_map(siv, id), "stop map")
                        };
                        if let Err(e) = r {
                            show_msg(siv, MsgType::Error, &std::format!("Failed to {}: {:?}", msg, e), |_| {});
                        }
                    }).with_name(N_BUTTON_MAP_ACTION))
                    .child(DummyView {})
                    .child(Button::new("remove", move |siv| {
                        let data_ref = siv.user_data::<TuiDataRef>().unwrap().clone();
                        let mut data = data_ref.borrow_mut();
                        if let Err(_) = data.remove_map(siv, id) {
                            show_msg(siv, MsgType::Error, "Failed to remove map", |_| {});
                        }
                    }).with_name(N_BUTTON_MAP_REMOVE))
                    .child(Button::new("edit", move |siv| {
                        let data_ref = siv.user_data::<TuiDataRef>().unwrap().clone();
                        let data = data_ref.borrow_mut();
                        let map_data = data.maps.get(&id).unwrap().clone();
                        // We should drop data here as we would take data again
                        // in map_editor.
                        drop(data);
                        if map_data.is_running {
                            show_msg(siv, MsgType::Warn, "Failed to edit a running map", |_| {});
                            return;
                        }
                        map_editor(siv, Some((map_data.name, map_data.info)), move |siv, v| {
                            if let Some(v) = v {
                                let data_ref = siv.user_data::<TuiDataRef>().unwrap().clone();
                                let mut data = data_ref.borrow_mut();
                                if let Err(e) = data.edit_map(siv, id, &v.0, &v.1) {
                                    show_msg(siv, MsgType::Error, &format!("Failed to edit map: {:?}", e), |_| {});
                                }
                            }
                        });
                    }).with_name(N_BUTTON_MAP_EDIT))
                    .with_name(&map_data.id.to_string());
        let mut listview_maps = siv.find_name::<ListView>(N_LISTVIEW_MAPS).unwrap();
        listview_maps.add_child(&map_data.name, item);
    }

    fn set_map_status(&mut self, map_data: &MapData, is_running: bool) {
        let siv = self.siv();
        let id = map_data.id.to_string();
        let mut item = siv.find_name::<LinearLayout>(&id).unwrap();
        let mut textview_status = item.find_name::<TextView>(N_TEXTVIEW_STATUS).unwrap();
        let mut btn_map_action = item.find_name::<Button>(N_BUTTON_MAP_ACTION).unwrap();

        std::assert_eq!(textview_status.get_content().source(), if is_running {N_STATUS_STOPPED} else {N_STATUS_RUNNING});

        if is_running {
            textview_status.set_content(N_STATUS_RUNNING);
            btn_map_action.set_label(N_ACTION_STOP);
        } else {
            textview_status.set_content(N_STATUS_STOPPED);
            btn_map_action.set_label(N_ACTION_START);
        }
    }

    fn remove_map(&mut self, map_data: &MapData) {
        let siv = self.siv();
        let id = map_data.id.to_string();
        let mut listview_maps = siv.find_name::<ListView>(N_LISTVIEW_MAPS).unwrap();
        if let Some(index) = list_view_get_child_index(&listview_maps, &id) {
            listview_maps.take_focus(Direction::up());
            listview_maps.remove_child(index);
        }
    }

    fn edit_map(&mut self, map_data: &MapData) {
        // TODO
        let siv = self.siv();
        let id = map_data.id.to_string();
        let mut listview_maps = siv.find_name::<ListView>(N_LISTVIEW_MAPS).unwrap();
        if let Some(index) = list_view_get_child_index(&listview_maps, &id) {
            let child =  listview_maps.row_mut(index);          
            if let ListChild::Row(label, v) = child {
                *label = map_data.name.clone();
                let namedview = v.downcast_mut::<NamedView<LinearLayout>>().unwrap();
                let mut textview_info = namedview.find_name::<TextView>(N_TEXTVIEW_INFO).unwrap();
                textview_info.set_content(map_data.info.to_string());
            } else {
                // This should not happen
                panic!("Expect ListRow, found Delimiter");
            }
        }
    }
}

impl CursivePlus for Cursive {
    fn siv(&mut self) -> &mut Cursive {
        self
    }
}

fn validate_ipv4(ip: &str) -> bool {
    if let Err(_) = Ipv4Addr::from_str(ip) {
        false
    } else {
        true
    }
}

fn list_view_get_child_index(list_view: &ListView, name: &str) -> Option<usize> {
    let mut index = 0;
    for item in list_view.children() {
        if let ListChild::Row(_, ref v) = item {
            let namedview = v.downcast_ref::<NamedView<LinearLayout>>().unwrap();
            if namedview.name() == name {
                // Matched!
                return Some(index);
            }
        }
        index += 1;
    }
    None
}
